package com.lyc.speechtranscriber;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.idst.nui.AsrResult;
import com.alibaba.idst.nui.BuildConfig;
import com.alibaba.idst.nui.CommonUtils;
import com.alibaba.idst.nui.Constants;
import com.alibaba.idst.nui.INativeNuiCallback;
import com.alibaba.idst.nui.KwsResult;
import com.alibaba.idst.nui.NativeNui;
import com.lyc.speechtranscriber.bean.HdAudioSource;
import com.lyc.speechtranscriber.bean.MediaProjectionProvider;

public class SpeechTranscriber {
    private final String TAG = "NuiSdkClient";
    private final Handler mHandler;
    private final HandlerThread mHanderThread;
    private final Context context;
    private final NativeNui nativeNui = new NativeNui();
    private final NuiCallback nuiCallback = new NuiCallback();
    private HyperAudioRecord mAudioRecorder;
    private TranscriberListener transcriberListener;
    private RecordingListener recordingListener;
    private boolean isInitSuccess;
    private String appKey = "";
    private String aliToken = "";
    private int silenceTime = 800;
    private boolean isEnablePunctuation = true;
    private boolean isRecording;
    private String taskId = "";
    private boolean isReset = false;
    private long lastTimeMill;
    private MediaProjectionProvider mediaProjectionProvider;
    private HdAudioSource mAudioSource = HdAudioSource.MIC;
    private boolean isAudioSourceSwitching = false;

    public SpeechTranscriber(Context context) {
        this.context = context;
        mHanderThread = new HandlerThread("process_thread");
        mHanderThread.start();
        mHandler = new Handler(mHanderThread.getLooper());
    }

    public boolean isRecording() {
        return isRecording;
    }

    public void setAppKey(String appKey) {
        this.appKey = appKey;
    }

    public void setAliToken(String aliToken) {
        this.aliToken = aliToken;
    }

    public void setTranscriberListener(TranscriberListener listener) {
        this.transcriberListener = listener;
    }

    public void setRecordingListener(RecordingListener recordingListener) {
        this.recordingListener = recordingListener;
    }

    public void setMediaProjectionProvider(MediaProjectionProvider mediaProjectionProvider) {
        this.mediaProjectionProvider = mediaProjectionProvider;
    }
    public void setAudioSource(HdAudioSource audioSource) {
        if (mAudioSource == audioSource)
            return;
        this.mAudioSource = audioSource;

        //如果正在录音,需要重新创建录音对象并开始录音
        if (isRecording){
            isAudioSourceSwitching = true;
            HyperAudioRecord oldAudioRecord = mAudioRecorder;
            //停止旧的录音对象
            if(oldAudioRecord != null){
                oldAudioRecord.stop();
                oldAudioRecord.release();
            }
            mAudioRecorder = null;

            HyperAudioRecord newAudioRecord = createAudioRecorder();
            //开始新的录音对象
            newAudioRecord.startRecording();

            //替换录音对象
            mAudioRecorder = newAudioRecord;

            isAudioSourceSwitching = false;
        }
    }

    public void startDialog(){
        if(isInitSuccess){
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    isReset = true;
                    int ret = nativeNui.startDialog(Constants.VadMode.TYPE_P2T,
                            genDialogParams());
                    Log.i(TAG, "start done with " + ret);
                }
            });
        }
    }

    public void stopDialog(){
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                long ret = nativeNui.stopDialog();
                Log.i(TAG, "cancel dialog " + ret + " end");
            }
        });
    }

    public void release(){
        nativeNui.release();
        mHandler.removeCallbacksAndMessages(null);
        mHanderThread.interrupt();
    }

    public boolean doInit() {

        //这里获得资源路径, 即工作路径
        //  内部通过context.getApplicationContext().getFilesDir().toString() + "/asr_my" 创建工作路径，
        //  例如 /data/user/0/mit.alibaba.nuidemo/files/asr_my
        String workspace = CommonUtils.getModelPath(context);

        //创建debug路径
        String debug_path = context.getExternalCacheDir().getAbsolutePath() + "/debug_" + System.currentTimeMillis();
        Utils.createDir(debug_path);

        //从nuisdk.aar中assets资源拷贝到workspace中
        CommonUtils.copyAssetsData(context);

        //检测是否开启麦克风权限
        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            //没有权限
            Log.e(TAG, "请检测是否开启麦克风权限");
            return false;
        }

        //SDK初始化
        int ret = nativeNui.initialize(nuiCallback, genInitParams(workspace, debug_path), Constants.LogLevel.LOG_LEVEL_VERBOSE, true);
        if (ret == Constants.NuiResultCode.SUCCESS) {
            isInitSuccess = true;
        } else {
            Log.e(TAG, "SDK初始化失败");
        }

        return isInitSuccess;
    }

    public void setParams(){
        if(isInitSuccess){
            //设置相关识别参数，具体参考API文档
            nativeNui.setParams(genParams());
        }
    }

    private String genDialogParams() {
        String params = "";
        try {
            JSONObject dialog_param = new JSONObject();
            //运行过程中可以在startDialog时更新参数，尤其是更新过期token
            dialog_param.put("app_key", appKey);
            dialog_param.put("token", aliToken);
            params = dialog_param.toString();
        } catch (JSONException e) {
            e.printStackTrace();
        }

        if(BuildConfig.DEBUG){
            Log.i(TAG, "dialog params: " + params);
        }
        return params;
    }

    private String genInitParams(String workpath, String debugpath) {
        String str = "";
        try{
            //获取token方式：

            JSONObject object = new JSONObject();

            //账号和项目创建
            //  ak_id ak_secret app_key如何获得,请查看https://help.aliyun.com/document_detail/72138.html
            object.put("app_key", appKey); // 必填

            //方法1：
            //  首先ak_id ak_secret app_key如何获得,请查看https://help.aliyun.com/document_detail/72138.html
            //  然后请看 https://help.aliyun.com/document_detail/466615.html 使用其中方案一获取临时凭证
            //  此方案简介: 远端服务器生成具有有效时限的临时凭证, 下发给移动端进行使用, 保证账号信息ak_id和ak_secret不被泄露
            //  获得Token方法(运行在APP服务端): https://help.aliyun.com/document_detail/450255.html?spm=a2c4g.72153.0.0.79176297EyBj4k
            object.put("token", aliToken); // 必填

            //方法2：
            //  STS获取临时凭证方法暂不支持

            //方法3：（强烈不推荐，存在阿里云账号泄露风险）
            //  参考Auth类的实现在端上访问阿里云Token服务获取SDK进行获取。请勿将ak/sk存在本地或端侧环境。
            //  此方法优点: 端侧获得Token, 无需搭建APP服务器。
            //  此方法缺点: 端侧获得ak/sk账号信息, 极易泄露。
//            JSONObject object = Auth.getAliYunTicket();

            object.put("device_id", Utils.getDeviceId()); // 必填, 推荐填入具有唯一性的id, 方便定位问题
            object.put("url", "wss://nls-gateway.cn-shanghai.aliyuncs.com:443/ws/v1"); // 默认
            //工作目录路径，SDK从该路径读取配置文件
            object.put("workspace", workpath); // 必填, 且需要有读写权限

            //当初始化SDK时的save_log参数取值为true时，该参数生效。表示是否保存音频debug，该数据保存在debug目录中，需要确保debug_path有效可写。
//            object.put("save_wav", "true");
            //debug目录，当初始化SDK时的save_log参数取值为true时，该目录用于保存中间音频文件。
            object.put("debug_path", debugpath);

            // FullMix = 0   // 选用此模式开启本地功能并需要进行鉴权注册
            // FullCloud = 1
            // FullLocal = 2 // 选用此模式开启本地功能并需要进行鉴权注册
            // AsrMix = 3    // 选用此模式开启本地功能并需要进行鉴权注册
            // AsrCloud = 4
            // AsrLocal = 5  // 选用此模式开启本地功能并需要进行鉴权注册
            // 这里只能选择FullMix和FullCloud
            object.put("service_mode", Constants.ModeFullCloud); // 必填

            str = object.toString();
        } catch (JSONException e) {
            e.printStackTrace();
        }

        // 注意! str中包含ak_id ak_secret token app_key等敏感信息, 实际产品中请勿在Log中输出这类信息！
        if(BuildConfig.DEBUG){
            Log.i(TAG, "InsideUserContext:" + str);
        }
        return str;
    }

    private String genParams() {
        String params = "";
        try {
            JSONObject nls_config = new JSONObject();
            nls_config.put("enable_voice_detection", true);//句义断句
            nls_config.put("enable_intermediate_result", true);//回调中间值
            nls_config.put("enable_punctuation_prediction", isEnablePunctuation);//标点
            nls_config.put("enable_inverse_text_normalization", false);//反向文本
            //nls_config.put("max_start_silence", 10000);
            nls_config.put("max_sentence_silence", silenceTime);//断句间隔
//            参数可根据实际业务进行配置
//            接口说明可见https://help.aliyun.com/document_detail/173528.html
//            查看 2.开始识别
//            nls_config.put("enable_punctuation_prediction", true);
//            nls_config.put("enable_inverse_text_normalization", true);
//            nls_config.put("max_sentence_silence", 800);
//            nls_config.put("enable_words", false);
//            nls_config.put("sample_rate", 16000);
//            nls_config.put("sr_format", "opus");
            JSONObject tmp = new JSONObject();
            tmp.put("nls_config", nls_config);
            tmp.put("service_type", Constants.kServiceTypeSpeechTranscriber); // 必填
//            如果有HttpDns则可进行设置
//            tmp.put("direct_ip", Utils.getDirectIp());
            params = tmp.toString();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return params;
    }

    @NonNull
    @SuppressLint("MissingPermission")
    private synchronized HyperAudioRecord createAudioRecorder(){
        if (mAudioRecorder != null){
            return mAudioRecorder;
        }
        return new HyperAudioRecord(mAudioSource, mediaProjectionProvider);
    }

    public void setMaxSentenceSilence(int silenceTime) {
        if(silenceTime < 200){
            silenceTime = 200;
        }else if(silenceTime > 6000){
            silenceTime = 6000;
        }
        this.silenceTime = silenceTime;
    }

    public void setEnablePunctuation(boolean enable) {
        this.isEnablePunctuation = enable;
    }

    class NuiCallback implements INativeNuiCallback{

        @Override
        public void onNuiEventCallback(Constants.NuiEvent event,
                                       final int resultCode,
                                       final int arg2,
                                       KwsResult kwsResult,
                                       AsrResult asrResult) {
            if(transcriberListener != null){
                transcriberListener.callback(event, asrResult);
            }

        }

        @Override
        public int onNuiNeedAudioData(byte[] bytes, int len) {
            if (mAudioRecorder == null) {
                Log.e(TAG, "AudioRecord not init");
                return 0;
            }
            if(isAudioSourceSwitching){
                return 0;
            }
            if(isReset){
                isReset = false;
                lastTimeMill = System.currentTimeMillis();
            }
            long currentTimeMill = System.currentTimeMillis();
            if(currentTimeMill - lastTimeMill < 500){
                //开始录音后的起始500毫秒内的声音不录入。
                return 0;
            }

            int ret = mAudioRecorder.read(bytes, len);
            Log.i(TAG, "ret = " + ret);
            if(recordingListener != null){
                recordingListener.readBytes(bytes);
            }
            return ret;
        }

        @Override
        public void onNuiAudioStateChanged(Constants.AudioState audioState) {
            Log.i(TAG, "onNuiAudioStateChanged");
            if (audioState == Constants.AudioState.STATE_OPEN) {
                isRecording = true;
                if(recordingListener != null){
                    taskId = String.valueOf(System.currentTimeMillis());
                    recordingListener.startRecording(taskId);
                }
                mAudioRecorder = createAudioRecorder();
                Log.i(TAG, "audio recorder start");
                mAudioRecorder.startRecording();
                Log.i(TAG, "audio recorder start done");

            } else if (audioState == Constants.AudioState.STATE_CLOSE) {
                Log.i(TAG, "audio recorder close");
                isRecording = false;
                mAudioRecorder.release();
                mAudioRecorder = null;
                if(recordingListener != null){
                    recordingListener.stopRecording(taskId);
                }

                if(transcriberListener != null){
                    transcriberListener.callback(Constants.NuiEvent.EVENT_TRANSCRIBER_COMPLETE, null);
                }

                if (mediaProjectionProvider != null){
                    mediaProjectionProvider.releaseMediaProjection();
                }
            } else if (audioState == Constants.AudioState.STATE_PAUSE) {
                Log.i(TAG, "audio recorder pause");
                isRecording = false;
                mAudioRecorder.stop();
                if(recordingListener != null){
                    recordingListener.stopRecording(taskId);
                }
                if (mediaProjectionProvider != null){
                    mediaProjectionProvider.releaseMediaProjection();
                }
            }
        }

        /**
         * 音频数据能量值回调，范围-160至0，一般用于UI展示语音动效
         */

        @Override
        public void onNuiAudioRMSChanged(float v) {
            if(transcriberListener != null){
                transcriberListener.onNuiAudioRMSChanged(v);
            }
        }

        @Override
        public void onNuiVprEventCallback(Constants.NuiVprEvent nuiVprEvent) {
        }
    }
}
